package com.gopay.common.security.SensitiveInfo.service.impl;

import com.gopay.common.cipher.algorithm.CipherAlgorithmContext;
import com.gopay.common.cipher.algorithm.baseAlgorithm.Decrypt;
import com.gopay.common.cipher.algorithm.baseAlgorithm.Encrypt;
import com.gopay.common.cipher.algorithm.impl.CipherAlgorithmWithCode;
import com.gopay.common.cipher.constants.EncryptType;
import com.gopay.common.cipher.constants.SensitiveInfoConstants;
import com.gopay.common.cipher.utils.CacheUtils;
import com.gopay.common.cipher.utils.DataKeyUtils;
import com.gopay.common.cipher.utils.KeyLockUtils;
import com.gopay.common.cipher.utils.KeyPropertiesUtils;
import com.gopay.common.cipher.vo.SecurityInfoKeyBox;
import com.gopay.common.cipher.vo.SecurityKeyInfo;
import com.gopay.common.constants.proccode.ProcCodeEnum;
import com.gopay.common.domain.sensitiveInfo.SecurityKeysStorage;
import com.gopay.common.exception.SensitiveInfo.SensitiveInfoKeyException;
import com.gopay.common.security.SensitiveInfo.dao.SecurityKeysStorageDao;
import com.gopay.common.security.SensitiveInfo.manager.SecurityKeysStorageManager;
import com.gopay.common.security.SensitiveInfo.service.SecurityKeysSupervise;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.*;

/**
 * 
 * @ClassName: SecurityKeysSuperviseImpl 
 * @Description: 监管密钥实现类 
 * @author zyt 
 * @date 2016年9月1日 下午2:50:03
 */
@Service("securityKeysSupervise")
public class SecurityKeysSuperviseImpl implements SecurityKeysSupervise{
	
	@Resource(name = "securityKeysStorageManager")
	private SecurityKeysStorageManager securityKeysStorageManager;
	
	@Resource(name = "securityKeysStorageDao")
	private SecurityKeysStorageDao securityKeysStorageDao;
	
	private static SecurityInfoKeyBox securityInfoKeyBox = new SecurityInfoKeyBox();

	private static final String MASTER_KEY_NAME = "mkey";
	
	private static final String MASTER_KEY_TYPE = "mkeytype";
	
	private static Logger logger = LoggerFactory.getLogger(SecurityKeysSuperviseImpl.class);
	
	private static SecurityKeyInfo currentKeyInfo = new SecurityKeyInfo();
	
	private static Map<String, SecurityKeyInfo> allKeyInfo = new HashMap<String, SecurityKeyInfo>();
	
	//字母与数字，特殊字符的集合，作为密钥的字符库
	private static char[] ALL_CHAR_LIST = {'0', '1', '2', '3','4', '5','6', '7','8', '9',
			'a', 'b','c', 'd', 'e', 'f','g', 'h','i', 'j','k', 'l','m', 'n', 'o', 'p','q', 'r','s', 't','u', 'v','w', 'x', 'y', 'z',
			'A', 'B','C', 'D', 'E', 'F','G', 'H','I', 'J','K', 'L','M', 'N', 'O', 'P','Q', 'R','S', 'T','U', 'V','W', 'X', 'Y', 'Z',
			'~', '!','@', '#', '$', '%','^', '&','*', '?'};
	
	//时间间隔
	private static final long PERIOD = 24 * 60 * 60 * 1000;
	//執行的時間：小時
	private static final int START_HOUR = 2;
	//執行的時間：分鐘
	private static final int START_MINITE = 0;
	//執行的時間：秒
	private static final int START_SECOND = 0;
	
	@PostConstruct 
	public void initData(){
		try {
			//loadProperties();
			loadAllKeyInfos();
		} catch (SensitiveInfoKeyException e) {
			logger.error("key supervise init data failed" + e);
		}
		//autoLoadKeys();
	}
	

	/**
	 *
	 * @Description: 	获取data key，
	 * 					用master key对加密的data key进行3DES界面操作，得到data key
	 * @return 			data key
	 * @author 			zyt
	 * @throws 			SensitiveInfoKeyException
	 */

	/**
	 *
	 * @Description: 	加載密鈅信息到内存
	 * @author 			zyt
	 * @throws 			SensitiveInfoKeyException
	 */
	private void loadAllKeyInfos() throws SensitiveInfoKeyException{
		allKeyInfo.clear();
		List<SecurityKeysStorage> allKeys = securityKeysStorageManager.getAllKeys();
		for(SecurityKeysStorage key : allKeys){
			SecurityKeyInfo keyInfo = buildInfoByKeySto(key);
			allKeyInfo.put(keyInfo.getPrefix(), keyInfo);
			if(StringUtils.equals(key.getStatus(), "01")){
				currentKeyInfo = keyInfo;
			}
		}
		securityInfoKeyBox.setCurrentKeyInfo(currentKeyInfo);
		securityInfoKeyBox.setAllKeyInfos(allKeyInfo);

		//modified by zyt,2017-7-12, 若项目中没有配置redis或redis连接异常，则不去设置redis中的密钥
		if(CacheUtils.checkCacheProp() && CacheUtils.checkCacheConncetion()) {
			DataKeyUtils.setKeyBox(securityInfoKeyBox);
		}
		//add by zyt,2017-7-11，设置tomcat内存中的备份密钥
		DataKeyUtils.setSecurityInfoKeyBox(securityInfoKeyBox);

		logger.info("key supervise init data success，size:" + allKeys.size());
	}

	/**
	 * 
	 * @Description: 		根据表中的密钥，获取密钥的信息 
	 * @param cipherDataKey	表中的密钥
	 * @return 				密钥信息
	 * @author 				zyt
	 * @throws 				SensitiveInfoKeyException 
	 */
	private SecurityKeyInfo buildInfoByKeySto(SecurityKeysStorage cipherDataKey) throws SensitiveInfoKeyException{
		SecurityKeyInfo info = new SecurityKeyInfo();
		info.setEncryptType(cipherDataKey.getEncryptType());
		info.setKeyVersion(cipherDataKey.getKeyVersion());
		info.setPrefix(getPrefixByKeySto(cipherDataKey));
		info.setKeyContent(cipherDataKey.getKeyContent());
		return info;
	}
	
	/**
	 * 
	 * @Description: 		根据表中的密钥，获取密文的前缀 
	 * @param cipherDataKey	表中的密钥
	 * @return 				密文前缀
	 * @author 				zyt
	 */
	private String getPrefixByKeySto(SecurityKeysStorage cipherDataKey){
		String prefix = "";
		if(cipherDataKey.getEncryptType() == EncryptType.THREE_DES_THREE_KEYS){
			prefix = SensitiveInfoConstants.ENCRYPT_MARK_3DES_3KEY  
					+ cipherDataKey.getKeyVersion() + "_" + SensitiveInfoConstants.ENCRYPT_COMMON_MARK;
		}
		
		return prefix;
	}
	
	/**
	 * 
	 * @Description: 	获取master key。
	 * 					暂时从应用服务器获取，后续可能需要修改为从加密机获取 
	 * @return 			master key
	 * @author			zyt
	 * @throws 			SensitiveInfoKeyException 
	 */
	private static String getMasterKey() throws SensitiveInfoKeyException{
		String key = KeyPropertiesUtils.getValueBykey(MASTER_KEY_NAME);
		try {
			Decrypt d = new Decrypt();
			return d.decrypt(key);
		} catch (Exception e) {
			logger.error("解密master key失败：" + e.getMessage());
			throw new SensitiveInfoKeyException(ProcCodeEnum.PROC_CODE_101E0001);
		}
	}
	
	/**
	 * 
	 * @Description: 	从配置文件读取对master key对data key的加密方式
	 * @return 			加密方式与EncryptType中的int值对应
	 * @author 			zyt
	 * @throws 			SensitiveInfoKeyException 
	 */
	private static String getMasterKeyType() throws SensitiveInfoKeyException{
		String keyType = KeyPropertiesUtils.getValueBykey(MASTER_KEY_TYPE);
		try {
			Decrypt d = new Decrypt();
			return d.decrypt(keyType);
		} catch (Exception e) {
			logger.error("解密master key type失败：" + e.getMessage());
			throw new SensitiveInfoKeyException(ProcCodeEnum.PROC_CODE_101E0001,"解密master key信息失败");
		}
	}
	/**
	 * 
	 * @Description: 	获取当前使用的，加密存储的datakey，需从数据库获取 
	 * @return 			正在使用的，加密存储的data key
	 * @author 			zyt
	 */
	private SecurityKeysStorage getCipherDataKey(){
		SecurityKeysStorage currentKey = securityKeysStorageManager.getCurrentKey();
		
		return currentKey;
	}
	
	@Override
	@Transactional
	public void changeMasterKey(String keyContent, EncryptType encryptType) throws SensitiveInfoKeyException {
		
		if(StringUtils.isBlank(keyContent)){
			throw new SensitiveInfoKeyException(ProcCodeEnum.PROC_CODE_101E0004);
		}
		//add by zyt, 2017-7-12，设置之前校验redis状态，如果redis状态异常，则抛出异常
		if(!CacheUtils.checkCacheProp()) {
			throw new SensitiveInfoKeyException(ProcCodeEnum.PROC_CODE_101E0007,
					"master key配置文件不存在，更换master key密钥失败");
		}
		if(!CacheUtils.checkCacheConncetion()) {
			throw new SensitiveInfoKeyException(ProcCodeEnum.PROC_CODE_101E0007,
					"redis 未启用，无法同步各个server的密钥，更换master key失败");
		}

		String currentMasterKey = getMasterKey();
		List<SecurityKeysStorage> allKeys = securityKeysStorageManager.getAllKeys();
		KeyLockUtils.lock();
		try {
			if(allKeys != null && allKeys.size() > 0){
				for(SecurityKeysStorage key : allKeys){
					String hisCipherKey = key.getKeyContent();
					EncryptType eType = CipherAlgorithmContext.getEncryptType(hisCipherKey);
					String hisKey = decryptDataKey(hisCipherKey, currentMasterKey, eType);
					
					String newKey = encryptDataKey(hisKey, keyContent, encryptType);
					
					key.setKeyContent(newKey);
					key.setLastUpdTs(new Date());
					securityKeysStorageDao.update(key);
				}
			}
			Encrypt e = new Encrypt();
			KeyPropertiesUtils.backupMasterKeyProperties();
			KeyPropertiesUtils.setValueBykey(MASTER_KEY_NAME, e.encrypt(keyContent));
			KeyPropertiesUtils.setValueBykey(MASTER_KEY_TYPE, e.encrypt(encryptType.name()));

			loadAllKeyInfos();
		} catch (SensitiveInfoKeyException se){
			throw se;
		} catch (Exception e) {
			logger.error("设置master key失败，加密失败:{}" , e);
			throw new SensitiveInfoKeyException(ProcCodeEnum.PROC_CODE_101E0003);
		} finally {
			KeyLockUtils.unLock();
		}
		logger.info("设置master key成功");
	}
	
	@Override
	public void changeRandomMasterKey(EncryptType encryptType) throws SensitiveInfoKeyException {
		String randomMasterKey = getRandomString(24);
		changeMasterKey(randomMasterKey, encryptType);
	}

	@Override
	@Transactional
	public void changeDataKey(String key, EncryptType encryptType, String keyVersion) throws SensitiveInfoKeyException {
		if(StringUtils.isBlank(key)){
			throw new SensitiveInfoKeyException(ProcCodeEnum.PROC_CODE_101E0004,"密钥信息不全，设置密钥失败");
		}
		//add by zyt, 2017-7-12，设置之前校验redis状态，如果redis状态异常，则抛出异常
		if(!CacheUtils.checkCacheProp()) {
			throw new SensitiveInfoKeyException(ProcCodeEnum.PROC_CODE_101E0007,
					"master key配置文件不存在，更换data key密钥失败");
		}
		if(!CacheUtils.checkCacheConncetion()) {
			throw new SensitiveInfoKeyException(ProcCodeEnum.PROC_CODE_101E0007,
					"redis 未启用，无法同步各个server的密钥，更换data key失败");
		}

		if(StringUtils.isBlank(keyVersion)){
			keyVersion = getNewKeyVersion();
		}
		SecurityKeysStorage currentKey = securityKeysStorageManager.getCurrentKey();
		if(currentKey != null){
			currentKey.setStatus("02");
			currentKey.setLastUpdTs(new Date());
			securityKeysStorageDao.update(currentKey);
		}
		String masterKey = getMasterKey();
		EncryptType masterType = EncryptType.valueOf(getMasterKeyType());
		SecurityKeysStorage newKey = new SecurityKeysStorage();
		try {
			newKey.setKeyContent(encryptDataKey(key, masterKey, masterType));
		} catch (Exception e) {
			throw new SensitiveInfoKeyException(ProcCodeEnum.PROC_CODE_101E0005,"加密data key信息失败");
		}
		newKey.setEncryptType(encryptType);
		newKey.setKeyVersion(keyVersion.trim());
		newKey.setCreatedTs(new Date());
		newKey.setStatus("01");
		securityKeysStorageDao.save(newKey);

		loadAllKeyInfos();

		/*String test = "zhaoyating";
		String encryptTest = SensitiveInfoUtils.smartEncryptWithCheck(test);
		logger.info("cipher text:" + encryptTest);
		logger.info("text: " + SensitiveInfoUtils.smartDecryptWithCheck(encryptTest));*/
		//currentKeyInfo = buildInfoByKeySto(newKey);
		
		logger.info("设置data key成功");
	}

	@Override
	public void changeRandomDataKey(EncryptType encryptType) throws SensitiveInfoKeyException {
		String randomDataKey = getRandomString(24);
		
		changeDataKey(randomDataKey, encryptType, null);
	}
	
	@Override
	public String getNewKeyVersion(){
		//String keyVersion = currentKeyInfo.getKeyVersion().trim();
		String keyVersion = getCurrentKeyVersion();
		BigDecimal bVersion = new BigDecimal(keyVersion.substring(1)).setScale(1);
		BigDecimal newBVersion = bVersion.add(new BigDecimal("0.1").setScale(1)).setScale(1);
		
		String newKeyVersion = "v" + newBVersion.toString();
		
		return newKeyVersion;
	}
	

	/**
	 * 
	 * @Description: 	获取当前密钥版本 
	 * @return 			当前密钥本版
	 * @author 			zyt
	 */
	private String getCurrentKeyVersion(){
		String currentKeyVersion = currentKeyInfo.getKeyVersion();
		if(StringUtils.isBlank(currentKeyVersion)){
			currentKeyVersion = securityKeysStorageManager.getCurrentKey().getKeyVersion();
		}
		
		return currentKeyVersion.trim();
	}
	
	/**
	 * 
	 * @Description: 		为datakey进行加密，并设置前缀
	 * @param dataKey		datakey的明文
	 * @param masterKey		master key
	 * @param encryptType	加密方式
	 * @return 				data key的密文
	 * @author 				zyt
	 */
	private String encryptDataKey(String dataKey, String masterKey, EncryptType encryptType) throws Exception{
		if (encryptType == EncryptType.THREE_DES_THREE_KEYS) {
			return SensitiveInfoConstants.ENCRYPT_MARK_3DES_3KEY + SensitiveInfoConstants.ENCRYPT_COMMON_MARK
					+ CipherAlgorithmWithCode.encrypt3DES3KEY(dataKey, masterKey);
		} else {
			return dataKey;
		}
	}
	
	/**
	 * 
	 * @Description: 		为加密的data key进行解密 
	 * @param cipherKey		data key的密文
	 * @param masterKey		master key
	 * @param encryptType	加密方式
	 * @return 				data key明文
	 * @author zyt
	 */
	private String decryptDataKey(String cipherKey, String masterKey, EncryptType encryptType) throws Exception{
		if (encryptType == EncryptType.THREE_DES_THREE_KEYS) {
			cipherKey = cipherKey.replace(SensitiveInfoConstants.ENCRYPT_MARK_3DES_3KEY 
					+ SensitiveInfoConstants.ENCRYPT_COMMON_MARK, "");
			return CipherAlgorithmWithCode.decrypt3DES3KEY(cipherKey, masterKey);
		} else {
			return cipherKey;
		}
	}


	
	/**
	 * 
	 * @Description: 	得到一个随机的字符 
	 * @return 			随机字符
	 * @author 			zyt
	 */
	private static char getSingleRandom(){
		Random r = new Random();
		int index = r.nextInt(ALL_CHAR_LIST.length);
		
		return ALL_CHAR_LIST[index];
	}
	
	@Override
	public String getRandomString(int length){
		if(length <= 0){
			return "";
		}
		StringBuffer resStr = new StringBuffer();
		for(int i = 0; i < length; i++){
			char c = getSingleRandom();
			resStr.append(c); 
		}
		
		return resStr.toString();
	}
	
	
	/**
	 * 
	 * @Description: 	定时自动重新加载所有密钥信息  
	 * @author zyt
	 */
	/*private void autoLoadKeys(){
		logger.info("自动加载密钥信息开始");
		Calendar calendar = Calendar.getInstance();
		calendar.set(Calendar.HOUR_OF_DAY, START_HOUR);
		calendar.set(Calendar.MINUTE, START_MINITE);
		calendar.set(Calendar.SECOND, START_SECOND);
		
		Date date = calendar.getTime();
		
		Date currentDate = new Date();
		
		long timeDelay = date.getTime() - currentDate.getTime();
		
		while(timeDelay <= 0){
			timeDelay = timeDelay + PERIOD;
		}
		
		Timer t = new Timer();
		t.schedule(new TimerTask(){
			public void run(){
				try {
					loadAllKeyInfos();
					logger.info("自动加载密钥信息成功");
				} catch (SensitiveInfoKeyException e) {
					logger.error("自动加载密钥失败：" + e);
				}
			}
		}, timeDelay, PERIOD);
	}*/
	
	
	public static void main(String[] args) {
		//System.out.println(getRandomString(24));
		/*Calendar c = Calendar.getInstance();
		c.set(Calendar.HOUR, 10);
		c.set(Calendar.MINUTE, 52);
		c.set(Calendar.SECOND, 10);
		
		//c.set(2016, 10, 13, 10, 47, 21);
		
		Date d = c.getTime();
		System.out.println(d);
		Timer t = new Timer();
		t.schedule(new TimerTask(){
			public void run(){
				System.out.println("定时器执行..");
			}
		}, d, PERIOD);*/
		
	}
}
